#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h> /* para setnonblocking() */

#include "Estructuras.h"
#include "includes/Mensajes.h"
#include "includes/Threads.h"
#include "includes/InterfacePPD.h"

stRaidData raidDataGlobal;


int iniciarComoServer()
{
	int fdListen;
	struct sockaddr_in serveraddr;
	int yes;
	

	raidDataGlobal.sfd_EspejarDisco = crearSenial(SIGUSR2);
	raidDataGlobal.sfd_ReplicarDatos = crearSenial(SIGUSR2);
	
	
	/* Crea el socket para listen*/
	if((raidDataGlobal.listener = socket(AF_INET, SOCK_STREAM, 0)) == -1)
	{
			perror("Server-socket() error lol!");
			exit(1);
	}

	/* Para las opciones del socket */
	if(setsockopt(raidDataGlobal.listener, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1)
	{
			perror("Server-setsockopt() error lol!");
			exit(1);
	}

	serveraddr.sin_family = AF_INET;
	serveraddr.sin_addr.s_addr = INADDR_ANY; /* IP cualquiera de la m?quina */
	serveraddr.sin_port = htons(PORT);
	memset(&(serveraddr.sin_zero), '\0', 8);


	if(bind(raidDataGlobal.listener, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) == -1) {
			perror("Server-bind() error!");
			exit(1);
	}

	if(listen(raidDataGlobal.listener, 10) == -1) {
			perror("Server-listen() error lol!");
			exit(1);
	}

	fdListen = raidDataGlobal.listener; 

	/*
	Creo el poll. +
	Creo los eventos que voy a esperar que ocurran en los sockets de mi pool.
	epoll_create() = Open an epoll file descriptor by requesting the kernel allocate an event backing store dimensioned for size descriptors
	ref: http://linux.die.net/man/2/epoll_create
	*/
	printf("5. Creando eventos\n");
	/*events = calloc(MAX_CON, sizeof(struct epoll_event));*/
	raidDataGlobal.ev.events = calloc(MAX_CON, sizeof(struct epoll_event));

	if ((raidDataGlobal.epfd = epoll_create(MAX_CON)) == -1)
	{
			perror("epoll_create");
			exit(1);
	}
	/*
	EPOLLIN = The associated file is available for read operations.
	ev.events = EPOLLIN; = indico que los eventos a los que quiero estar atenta son los de lectura del socket.
	O sea .. cuando alguien escriba en mi socket quiero que el poll me diga.."este socket requiere ser LEIDO"
	*/
	raidDataGlobal.ev.events = EPOLLIN;
	raidDataGlobal.ev.data.fd = fdListen;

	/* epoll_ctl() controla si se realizo la operacion del 2? parametro en los descriptores del pool.
	EPOLL_CTL_ADD = Add the target file descriptor fd to the epoll descriptor epfd and associate the event event with the internal file linked to fd.
	ref: http://linux.die.net/man/2/epoll_ctl
	
	con EPOLL_CTL_ADD agrego al pool el descriptor del socket en listen */
	if (epoll_ctl(raidDataGlobal.epfd, EPOLL_CTL_ADD, fdListen, &(raidDataGlobal.ev)) < 0) 
	{
			perror("epoll_ctl");
			exit(1);
	}

	/* Agrego al pool el descriptor de la señal */
	if (epoll_ctl(raidDataGlobal.epfd, EPOLL_CTL_ADD, raidDataGlobal.sfd_EspejarDisco, &(raidDataGlobal.ev)) < 0)
	{
			perror("epoll_ctl");
			exit(1);
	}
	
	if (epoll_ctl(raidDataGlobal.epfd, EPOLL_CTL_ADD, raidDataGlobal.sfd_ReplicarDatos, &(raidDataGlobal.ev)) < 0)
	{
			perror("epoll_ctl");
			exit(1);
	}
	
	
	return EXIT_SUCCESS;
}



int aceptarConexion()
{
	int newfd;
	int addrlen;	
	struct sockaddr clientaddr;
	
	addrlen = sizeof(clientaddr);
	if((newfd = accept(raidDataGlobal.listener, (struct sockaddr *)&clientaddr, &addrlen)) == -1)
		perror("Server-accept() error !");
	else
	{
		printf("7. aceptando cliente %d\n", newfd);
		printf("Server-accept() is OK...\n");
		raidDataGlobal.ev.events = EPOLLIN; /* indico el tipo de evento (de READ) al que quiero estar atenta en el socket */
		raidDataGlobal.ev.data.fd = newfd;  /* agrego el descriptor a la cola de eventos */
		if (epoll_ctl(raidDataGlobal.epfd, EPOLL_CTL_ADD, newfd, &(raidDataGlobal.ev)) < 0)
		{/* agrego el descriptor al pool */
				perror("epoll_ctl");
				exit(1);
		}
	}

	return EXIT_SUCCESS;
}


int cerrarOperaciones(int client_fd)
{
	/* EPOLL_CTL_DEL = lo limpio del pool
	EPOLL_CTL_DEL = Remove the target file descriptor fd from the epoll file descriptor, epfd.
	The event is ignored and can be NULL.
	ref: http://linux.die.net/man/2/epoll_ctl */
	printf("cerrando socket %d\n", client_fd);
	
	if (epoll_ctl(raidDataGlobal.epfd, EPOLL_CTL_DEL, client_fd, &(raidDataGlobal.ev)) < 0)
	{
		perror("epoll_ctl");
	}
	
	close(client_fd);
	
	return EXIT_SUCCESS;
}



int atenderHandShake(stMsgNIPC *mensaje, int iSocket)
{
	stMsgNIPC handShakeError;
	int totalSectores; /* en realidad este dato me viene en el payload hay q parsearlo! */
	int resul;

	/***  PPD ***************************/
	if(!strcmp(mensaje->Payload, MSG_HANDSHAKE_PPD))
	{
		printf("Se conecto un proceso PPD, socket:%d\n", iSocket);
				
		if(raidDataGlobal.listadoThreads == NULL) /* Este es el PRIMER PPD */
		{
			/* TODO En el mensaje me viene el HANDSHAKE y la cantidad de sectores totales que tiene el PPD */
			raidDataGlobal.totalSectores = totalSectores;
			espejarDisco();
			return EXIT_SUCCESS;
		}
		
		asignarThread(iSocket);
	}

	/***  CONSOLA  ***************************/
	else if(!strcmp(mensaje->Payload, MSG_HANDSHAKE_CONSOLA))
	{
		printf("Se conecto una Consola, socket:%d\n", iSocket);
	}

	/***  FILE SYSTEM  ***************************/
	else if(!strcmp(mensaje->Payload, MSG_HANDSHAKE_FS))
	{
		printf("Se conecto un proceso File System, socket:%d\n", iSocket);
		
		if(raidDataGlobal.listadoThreads == NULL) /* No tengo PPD */
		{
			/* Rechazo el handShake */
			if(handShakeError.Payload = malloc(sizeof(uint32_t)) == NULL)
				return EXIT_ERROR_MEMORY;

			armarMensaje(&handShakeError, HANDSHAKE_REFUSED, 0);

			enviarMensaje(iSocket, &handShakeError);

			return HANDSHAKE_REFUSED;
		}
		/* Si es un FS y tengo PPDs no hago nada..ya me va a enviar pedidos..*/
	}
	else 
		return HANDSHAKE_ERROR;

	return HANDSHAKE_OK;
}


void setnonblocking(int sock)
{
	int opts;

	opts = fcntl(sock,F_GETFL);
	if (opts < 0) {
		perror("fcntl(F_GETFL)");
		exit(EXIT_FAILURE);
	}
	opts = (opts | O_NONBLOCK);
	if (fcntl(sock,F_SETFL,opts) < 0) {
		perror("fcntl(F_SETFL)");
		exit(EXIT_FAILURE);
	}
	return;
}
